Access to Canadian census data through the CensusMapper API

https://mountainmath.github.io/cancensus/reference/get_census.html

Source Census data and boundary geographies are reproduced and distributed on an “as is” basis with the permission of Statistics Canada (Statistics Canada 2006; 2011; 2016).

Details get_census_geometry is a convenience function that retrieves only Census geography boundaries.

For help selecting regions and vectors, see list_census_regions and list_census_vectors, or check out the interactive selection tool at https://censusmapper.ca/api.

Examples

# install.packages("cancensus")
library(cancensus)
cancensus.cache_path = 'G:\\project\\data'
# Query the API for data on dwellings in Vancouver, at the census subdivision
# level:
# NOT RUN {
census_data <- get_census(dataset='CA16', regions=list(CMA="59933"),
                          vectors=c("v_CA16_408","v_CA16_409","v_CA16_410"),
                          level='CSD')


# Query the API for data on dwellings in Vancouver, at the census subdivision
# level, and return the associated geography files in \code{sf} format:
census_data <- get_census(dataset='CA16', regions=list(CMA="59933"),
                          vectors=c("v_CA16_408","v_CA16_409","v_CA16_410"),
                          level='CSD', geo_format = "sf")

# Make the same query, but return geography in \code{sp} format:
census_data <- get_census(dataset='CA16', regions=list(CMA="59933"),
                          vectors=c("v_CA16_408","v_CA16_409","v_CA16_410"),
                          level='CSD', geo_format = "sf")

# Make the same query, but this time drop descriptive vector names:
census_data <- get_census(dataset='CA16', regions=list(CMA="59933"),
                          vectors=c("v_CA16_408","v_CA16_409","v_CA16_410"),
                          level='CSD', geo_format = "sf", labels="short")

# Get details for truncated vectors:
label_vectors(census_data)
# }# NOT RUN {
# Query the API for census subdivision boundary geometry within Vancouver.
vc_csds <- get_census_geometry(dataset='CA16', regions=list(CMA="59933"),
                               level='CSD', geo_format = "sf")
# }

StatCan API’s Discovered

https://www.mytinyshinys.com/2017/08/09/statcanapi/

Let’s load the libraries and see what is available for the all indicators option

library(httr)
library(jsonlite)
library(listviewer)
library(tidyverse)
library(stringr)
library(plotly)

The listviewer package, an htmlwidget from the ubiquitous Kent Russell and others, provides a great way to explore lists

url <- "http://www.statcan.gc.ca/sites/json/ind-all.json"
response <-  GET(url)
parsed <- fromJSON(content(response, "text"), simplifyVector = FALSE)
No encoding supplied: defaulting to UTF-8.
jsonedit(parsed)

If you drill down “results > indicators > 0 > title > en” you can see the title of one of the more than thousand indicators. I believe they get added consecutively to the top but at the time of writing the first one was let’s see what we can output from this list.

# start deeper into the nested list
ind_list <- parsed$results$indicators
# Now use purrr to create atomic vectors
registry_number <- map_chr(ind_list, "registry_number")
indicator_number <- map_chr(ind_list, "indicator_number")
geo_code <- map_chr(ind_list, "geo_code")
source <- map_chr(ind_list, "source")
themes <- map_chr(ind_list, "themes")
release_date <- map_chr(ind_list, "release_date")
## For those where we need to go down a further level we can use a vector
## either numbered
title <- map_chr(ind_list, c(4, 1))
#or text
refper <- map_chr(ind_list, c("refper", "en"))
value <- map_chr(ind_list, c("value", "en"))
daily_url <- map_chr(ind_list, c("daily_url", "en"))
daily_title <- map_chr(ind_list, c("daily_title", "en"))
## combine into a data.frame
l <-
  list(
    registry_number = registry_number,
    indicator_number = indicator_number,
    geo_code = geo_code,
    source = source,
    themes = themes,
    release_date = release_date,
    title = title ,
    refper = refper,
    value = value,
    daily_url = daily_url,
    daily_title = daily_title
  )
indices.df <- as_tibble(l)
#and display in a table with selected columns
indices.df %>%
  select(geo_code,source,themes,title,value) %>% 
  DT::datatable(width=600,
    class = 'compact stripe hover row-border order-column',
    rownames = FALSE,
    options = list(
      paging = TRUE,
      searching = TRUE,
      info = FALSE
    )
  )
ind_list[[1]]
$`registry_number`
[1] 18584

$indicator_number
[1] 1

$geo_code
[1] 0

$title
$title$`en`
[1] "Total tourism spending on culture products, Canada"

$title$fr
[1] "Dépenses touristiques totales en produits de la culture, Canada"


$value
$value$`en`
[1] "$1.7 billion"

$value$fr
[1] "1,7 milliard de dollars"


$refper
$refper$`en`
[1] "2016"

$refper$fr
[1] "2016"


$daily_url
$daily_url$`en`
[1] "/daily-quotidien/180601/dq180601a-eng.htm"

$daily_url$fr
[1] "/daily-quotidien/180601/dq180601a-fra.htm"


$daily_title
$daily_title$`en`
[1] "Tourism spending on culture and sport products"

$daily_title$fr
[1] "Dépenses touristiques en produits de la culture et du sport"


$source
[1] "0"

$themes
[1] "*3764**3955**3961**4007*"

$release_date
[1] "2018-06-01"

$growth_rate
$growth_rate$`growth`
$growth_rate$`growth`$`en`
[1] "3.0"

$growth_rate$`growth`$fr
[1] "3,0"


$growth_rate$arrow_direction
[1] 1

$growth_rate$details
$growth_rate$details$`en`
[1] "(annual change)"

$growth_rate$details$fr
[1] "(variation annuelle)"
head(indices.df)

You can search for an item of interest e.g try “Potato” and you can see that there is one entry which appears to show 344,884 acres of Potatoes were planted in Canada this year, more than enough to cover Phoenix’s city limits

Looking back at the listviewer we can see that two of the table columns geo_code and themes appear to have equivalent raw data. Let’s tabulize them as well. It’s easier the second time through. For any Francophiles, just swap in the French alternative

geo_list <- parsed$results$geo
geo_code <- map_chr(geo_list, "geo_code")
geo_name <- map_chr(geo_list, c(2,1))
l <-
  list(
    geo_code=geo_code,
    geo_name=geo_name
    )
geo.df <- as_tibble(l)
geo.df %>%
  DT::datatable(
    class = 'compact stripe hover row-border order-column',
    rownames = FALSE,
    options = list(
      paging = TRUE,
      searching = TRUE,
      info = FALSE
    )
  )
## similar for themes - probably a map_df alternative
theme_list <- parsed$results$themes_en
theme_code <- map_chr(theme_list, 1)
theme_name <- map_chr(theme_list, 2)
l <-
  list(
   theme_code=theme_code,
    theme_name=theme_name
    )
theme.df <- as_tibble(l)
theme.df %>%
  DT::datatable(
    class = 'compact stripe hover row-border order-column',
    rownames = FALSE,
    options = list(
      paging = TRUE,
      searching = TRUE,
      info = FALSE
    )
  )

we can now link the geo data.frames to make the tabe more meaningful

indices.df %>% 
  left_join(geo.df) %>% 
  select(title,refper,geo_name,themes,value,source)%>%
                         DT::datatable(class='compact stripe hover row-border order-column',rownames=FALSE,options= list(paging = TRUE, searching = TRUE,info=FALSE))
Joining, by = "geo_code"

Type in ‘one-person’ and you will see that the Proportion of one-person households ranges by province from 18.9% in Nunavut to 33.3% in Quebec

Now lets search for indicators relating to the theme of ‘Agriculture’ which has a code of 920. Enter 920 in the search and you can find out if vegetables are worth more to the Canadian economy than fruits

Note the final column, source. If you enter the fruits value ‘10009’ into the CANSIM search field you will get forwarded to a table from which the underlying indicator has been extracted

NB This search/browse page(http://www5.statcan.gc.ca/cansim/a01?lang=eng) also has links to all tables, not just those for which there are indicators. These would need to be scraped, currently

This is a subset of a data table with provincial breakdowns over a greater time period

The CANSIM process is that you manipulate on-line the data you want and then you can download a csv. So, if, for example, all you were interested in was tonnage of pears from PEI for the years 2001-2006 (answer not much) this might be the best way to proceed

However, often it is better just to download all potentially-relevant data and then do some exploratory analyses within R. This is feasible but still needs a few clicks and moving the downloaded file to the appropriate folder and then importing it into R. I know, I want the easy life

Enter CANSIM2R an R package which ‘Directly Extracts Complete CANSIM Data Tables’. This was developed a couple of years ago by Marco Lugo when he was at the University of Montreal and has a couple of functions - one of which, getCANSIM(), extracts a complete CANSIM (Statistics Canada) data table and converts it into a readily usable panel (wide) format.

I did not find this a particulaly useful end-product (try the vignette for an example) but the behind-the-scenes code was valuable. It was PT (Pre-Tidyverse) but works just fine. For some reason the fruit table code did not work so I substituted ….

Funding of Research and development expenditures in the higher education sector

CANSIM2R

https://cran.r-project.org/web/packages/CANSIM2R/index.html

https://www150.statcan.gc.ca/n1/tbl/csv/27100025-eng.zip

http://www20.statcan.gc.ca/tables-tableaux/cansim/csv/

library(Hmisc)
library(utils)
#############
createStatCanVariables <- function(df){
  VectorPosition <- match("Vector",names(df))
  #Only create new variable if there is more than one column from StatCan
  if(VectorPosition > 4) df$StatCanVariable <- apply(df[,c(3:(VectorPosition-1))], 1, function(x) paste(x, collapse = "; "))
  else df$StatCanVariable <- df[,3]
  return(df)
}
###################
downloadCANSIM <- function(cansimTableNumber, raw = FALSE, lang = "eng"){
  # validation of the lang parameter
  # thanks to Professor Jean-Herman Guay (Université de Sherbrooke) for suggesting the inclusion of French data labels
  separator = ','
  if(lang == "eng") lang = "-eng"
  else if(lang == "fra" || lang == "fr"){
    lang = "-fra"
    separator = ';'
  } 
  else {
    print("Only English (eng) and French (fra) are accepted values for lang. Defaulting to English.")
    lang = "-eng"
  }
  
  temp <- tempfile() # create a temporary file to store the downloaded data
  # create the url to download the CANSIM data according to the user's needs
  url <- "https://www150.statcan.gc.ca/n1/en/tbl/csv/"
  cansimTableNumber <- gsub('-', '', cansimTableNumber)
  cansimTableNumberString <- sprintf("%08d", as.numeric(cansimTableNumber)) #Put the correct amount of leading zeroes; paste0 uses as.character which truncates leading zeroes from integers (special thanks to Soheil soheil Mahmoodzadeh for reporting the bug)
  filename <- paste0(cansimTableNumberString, lang)
  csv_filename <- paste0(cansimTableNumberString, ".csv")
  url <- paste0(url, filename, ".zip")
  
  tryCatch(
    {
    download(url, temp, quiet = TRUE, mode = "wb") # from the downloader package, easily handles cross-plaform https requests, wrapper for download.file
    },
    error=function(err){ return(-1) },
    warning=function(warn){ return(-1) }
    )
  temp_filesize <- file.info(temp)$size
  
  if(is.na(temp_filesize) || temp_filesize == 0) return(NA) # file is non-existent, exit prematurely
  
  data <- read.csv(unz(temp, csv_filename), stringsAsFactors = FALSE, sep = separator, encoding = "UTF-8")
  unlink(temp)
  if(raw == TRUE) return(data) #if raw equals TRUE, then the raw download is returned; functionality suggested by Soheil Mahmoodzadeh
  names(data) <- iconv(names(data), to='ASCII//TRANSLIT') # remove accents from variable names
  
  data$DGUID <- NULL
  data$IDENTIFICATEUR.D.UNITE.DE.MESURE <- NULL
  data$UOM_ID <- NULL
  data$SCALAR_FACTOR <- NULL
  data$FACTEUR.SCALAIRE <- NULL
  data$SCALAR_ID <- NULL
  data$IDENTIFICATEUR.SCALAIRE <- NULL
  data <- createStatCanVariables(data)
  
  data$VECTOR <- NULL
  data$VECTEUR <- NULL
  data$COORDINATES <- NULL
  data$COORDONEES <- NULL
  if(lang == '-fra') suppressWarnings(data$VALEUR <- as.numeric(data$VALEUR))
  else               suppressWarnings(data$VALUE <- as.numeric(data$VALUE))
  return(data)
}
df_raw <- downloadCANSIM(00010009)
# df_raw <- downloadCANSIM(3580162)
df_raw %>%
    DT::datatable(class='compact stripe hover row-border order-column',rownames=FALSE,options= list(paging = TRUE, searching = TRUE,info=FALSE))
 logi NA
Error in DT::datatable(., class = "compact stripe hover row-border order-column",  : 
  'data' must be 2-dimensional (e.g. data frame or matrix)
head(df_raw)
[1] NA
LS0tDQp0aXRsZTogIkNhbmFkaWFuIGNlbnN1cyBkYXRhIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KQWNjZXNzIHRvIENhbmFkaWFuIGNlbnN1cyBkYXRhIHRocm91Z2ggdGhlIENlbnN1c01hcHBlciBBUEkNCg0KaHR0cHM6Ly9tb3VudGFpbm1hdGguZ2l0aHViLmlvL2NhbmNlbnN1cy9yZWZlcmVuY2UvZ2V0X2NlbnN1cy5odG1sDQoNCg0KU291cmNlDQpDZW5zdXMgZGF0YSBhbmQgYm91bmRhcnkgZ2VvZ3JhcGhpZXMgYXJlIHJlcHJvZHVjZWQgYW5kIGRpc3RyaWJ1dGVkIG9uIGFuICJhcyBpcyIgYmFzaXMgd2l0aCB0aGUgcGVybWlzc2lvbiBvZiBTdGF0aXN0aWNzIENhbmFkYSAoU3RhdGlzdGljcyBDYW5hZGEgMjAwNjsgMjAxMTsgMjAxNikuDQoNCkRldGFpbHMNCmdldF9jZW5zdXNfZ2VvbWV0cnkgaXMgYSBjb252ZW5pZW5jZSBmdW5jdGlvbiB0aGF0IHJldHJpZXZlcyBvbmx5IENlbnN1cyBnZW9ncmFwaHkgYm91bmRhcmllcy4NCg0KRm9yIGhlbHAgc2VsZWN0aW5nIHJlZ2lvbnMgYW5kIHZlY3RvcnMsIHNlZSBsaXN0X2NlbnN1c19yZWdpb25zIGFuZCBsaXN0X2NlbnN1c192ZWN0b3JzLCBvciBjaGVjayBvdXQgdGhlIGludGVyYWN0aXZlIHNlbGVjdGlvbiB0b29sIGF0IGh0dHBzOi8vY2Vuc3VzbWFwcGVyLmNhL2FwaS4NCg0KRXhhbXBsZXMNCg0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoImNhbmNlbnN1cyIpDQpsaWJyYXJ5KGNhbmNlbnN1cykNCmBgYA0KDQoNCg0KYGBge3J9DQpjYW5jZW5zdXMuY2FjaGVfcGF0aCA9ICdHOlxccHJvamVjdFxcZGF0YScNCmBgYA0KDQoNCmBgYHtyfQ0KIyBRdWVyeSB0aGUgQVBJIGZvciBkYXRhIG9uIGR3ZWxsaW5ncyBpbiBWYW5jb3V2ZXIsIGF0IHRoZSBjZW5zdXMgc3ViZGl2aXNpb24NCiMgbGV2ZWw6DQojIE5PVCBSVU4gew0KY2Vuc3VzX2RhdGEgPC0gZ2V0X2NlbnN1cyhkYXRhc2V0PSdDQTE2JywgcmVnaW9ucz1saXN0KENNQT0iNTk5MzMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVjdG9ycz1jKCJ2X0NBMTZfNDA4Iiwidl9DQTE2XzQwOSIsInZfQ0ExNl80MTAiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWw9J0NTRCcpDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQoNCiMgUXVlcnkgdGhlIEFQSSBmb3IgZGF0YSBvbiBkd2VsbGluZ3MgaW4gVmFuY291dmVyLCBhdCB0aGUgY2Vuc3VzIHN1YmRpdmlzaW9uDQojIGxldmVsLCBhbmQgcmV0dXJuIHRoZSBhc3NvY2lhdGVkIGdlb2dyYXBoeSBmaWxlcyBpbiBcY29kZXtzZn0gZm9ybWF0Og0KY2Vuc3VzX2RhdGEgPC0gZ2V0X2NlbnN1cyhkYXRhc2V0PSdDQTE2JywgcmVnaW9ucz1saXN0KENNQT0iNTk5MzMiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVjdG9ycz1jKCJ2X0NBMTZfNDA4Iiwidl9DQTE2XzQwOSIsInZfQ0ExNl80MTAiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWw9J0NTRCcsIGdlb19mb3JtYXQgPSAic2YiKQ0KDQojIE1ha2UgdGhlIHNhbWUgcXVlcnksIGJ1dCByZXR1cm4gZ2VvZ3JhcGh5IGluIFxjb2Rle3NwfSBmb3JtYXQ6DQpjZW5zdXNfZGF0YSA8LSBnZXRfY2Vuc3VzKGRhdGFzZXQ9J0NBMTYnLCByZWdpb25zPWxpc3QoQ01BPSI1OTkzMyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB2ZWN0b3JzPWMoInZfQ0ExNl80MDgiLCJ2X0NBMTZfNDA5Iiwidl9DQTE2XzQxMCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbD0nQ1NEJywgZ2VvX2Zvcm1hdCA9ICJzZiIpDQoNCiMgTWFrZSB0aGUgc2FtZSBxdWVyeSwgYnV0IHRoaXMgdGltZSBkcm9wIGRlc2NyaXB0aXZlIHZlY3RvciBuYW1lczoNCmNlbnN1c19kYXRhIDwtIGdldF9jZW5zdXMoZGF0YXNldD0nQ0ExNicsIHJlZ2lvbnM9bGlzdChDTUE9IjU5OTMzIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHZlY3RvcnM9Yygidl9DQTE2XzQwOCIsInZfQ0ExNl80MDkiLCJ2X0NBMTZfNDEwIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVsPSdDU0QnLCBnZW9fZm9ybWF0ID0gInNmIiwgbGFiZWxzPSJzaG9ydCIpDQoNCiMgR2V0IGRldGFpbHMgZm9yIHRydW5jYXRlZCB2ZWN0b3JzOg0KbGFiZWxfdmVjdG9ycyhjZW5zdXNfZGF0YSkNCiMgfSMgTk9UIFJVTiB7DQojIFF1ZXJ5IHRoZSBBUEkgZm9yIGNlbnN1cyBzdWJkaXZpc2lvbiBib3VuZGFyeSBnZW9tZXRyeSB3aXRoaW4gVmFuY291dmVyLg0KdmNfY3NkcyA8LSBnZXRfY2Vuc3VzX2dlb21ldHJ5KGRhdGFzZXQ9J0NBMTYnLCByZWdpb25zPWxpc3QoQ01BPSI1OTkzMyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVsPSdDU0QnLCBnZW9fZm9ybWF0ID0gInNmIikNCiMgfQ0KYGBgDQoNCg0KIyMgU3RhdENhbiBBUEkncyBEaXNjb3ZlcmVkDQoNCmh0dHBzOi8vd3d3Lm15dGlueXNoaW55cy5jb20vMjAxNy8wOC8wOS9zdGF0Y2FuYXBpLw0KDQoNCg0KTGV04oCZcyBsb2FkIHRoZSBsaWJyYXJpZXMgYW5kIHNlZSB3aGF0IGlzIGF2YWlsYWJsZSBmb3IgdGhlIGFsbCBpbmRpY2F0b3JzIG9wdGlvbg0KDQoNCg0KYGBge3J9DQpsaWJyYXJ5KGh0dHIpDQpsaWJyYXJ5KGpzb25saXRlKQ0KbGlicmFyeShsaXN0dmlld2VyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCg0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShwbG90bHkpDQpgYGANCg0KDQpUaGUgbGlzdHZpZXdlciBwYWNrYWdlLCBhbiBodG1sd2lkZ2V0IGZyb20gdGhlIHViaXF1aXRvdXMgS2VudCBSdXNzZWxsIGFuZCBvdGhlcnMsIHByb3ZpZGVzIGEgZ3JlYXQgd2F5IHRvIGV4cGxvcmUgbGlzdHMNCg0KDQoNCmBgYHtyfQ0KdXJsIDwtICJodHRwOi8vd3d3LnN0YXRjYW4uZ2MuY2Evc2l0ZXMvanNvbi9pbmQtYWxsLmpzb24iDQpyZXNwb25zZSA8LSAgR0VUKHVybCkNCg0KDQpwYXJzZWQgPC0gZnJvbUpTT04oY29udGVudChyZXNwb25zZSwgInRleHQiKSwgc2ltcGxpZnlWZWN0b3IgPSBGQUxTRSkNCmpzb25lZGl0KHBhcnNlZCkNCmBgYA0KDQoNCklmIHlvdSBkcmlsbCBkb3duIOKAnHJlc3VsdHMgPiBpbmRpY2F0b3JzID4gMCA+IHRpdGxlID4gZW7igJ0geW91IGNhbiBzZWUgdGhlIHRpdGxlIG9mIG9uZSBvZiB0aGUgbW9yZSB0aGFuIHRob3VzYW5kIGluZGljYXRvcnMuIEkgYmVsaWV2ZSB0aGV5IGdldCBhZGRlZCBjb25zZWN1dGl2ZWx5IHRvIHRoZSB0b3AgYnV0IGF0IHRoZSB0aW1lIG9mIHdyaXRpbmcgdGhlIGZpcnN0IG9uZSB3YXMNCmxldOKAmXMgc2VlIHdoYXQgd2UgY2FuIG91dHB1dCBmcm9tIHRoaXMgbGlzdC4gDQoNCg0KDQpgYGB7cn0NCg0KIyBzdGFydCBkZWVwZXIgaW50byB0aGUgbmVzdGVkIGxpc3QNCmluZF9saXN0IDwtIHBhcnNlZCRyZXN1bHRzJGluZGljYXRvcnMNCg0KIyBOb3cgdXNlIHB1cnJyIHRvIGNyZWF0ZSBhdG9taWMgdmVjdG9ycw0KDQpyZWdpc3RyeV9udW1iZXIgPC0gbWFwX2NocihpbmRfbGlzdCwgInJlZ2lzdHJ5X251bWJlciIpDQppbmRpY2F0b3JfbnVtYmVyIDwtIG1hcF9jaHIoaW5kX2xpc3QsICJpbmRpY2F0b3JfbnVtYmVyIikNCmdlb19jb2RlIDwtIG1hcF9jaHIoaW5kX2xpc3QsICJnZW9fY29kZSIpDQpzb3VyY2UgPC0gbWFwX2NocihpbmRfbGlzdCwgInNvdXJjZSIpDQp0aGVtZXMgPC0gbWFwX2NocihpbmRfbGlzdCwgInRoZW1lcyIpDQpyZWxlYXNlX2RhdGUgPC0gbWFwX2NocihpbmRfbGlzdCwgInJlbGVhc2VfZGF0ZSIpDQoNCiMjIEZvciB0aG9zZSB3aGVyZSB3ZSBuZWVkIHRvIGdvIGRvd24gYSBmdXJ0aGVyIGxldmVsIHdlIGNhbiB1c2UgYSB2ZWN0b3INCiMjIGVpdGhlciBudW1iZXJlZA0KdGl0bGUgPC0gbWFwX2NocihpbmRfbGlzdCwgYyg0LCAxKSkNCiNvciB0ZXh0DQpyZWZwZXIgPC0gbWFwX2NocihpbmRfbGlzdCwgYygicmVmcGVyIiwgImVuIikpDQp2YWx1ZSA8LSBtYXBfY2hyKGluZF9saXN0LCBjKCJ2YWx1ZSIsICJlbiIpKQ0KZGFpbHlfdXJsIDwtIG1hcF9jaHIoaW5kX2xpc3QsIGMoImRhaWx5X3VybCIsICJlbiIpKQ0KZGFpbHlfdGl0bGUgPC0gbWFwX2NocihpbmRfbGlzdCwgYygiZGFpbHlfdGl0bGUiLCAiZW4iKSkNCg0KIyMgY29tYmluZSBpbnRvIGEgZGF0YS5mcmFtZQ0KbCA8LQ0KICBsaXN0KA0KICAgIHJlZ2lzdHJ5X251bWJlciA9IHJlZ2lzdHJ5X251bWJlciwNCiAgICBpbmRpY2F0b3JfbnVtYmVyID0gaW5kaWNhdG9yX251bWJlciwNCiAgICBnZW9fY29kZSA9IGdlb19jb2RlLA0KICAgIHNvdXJjZSA9IHNvdXJjZSwNCiAgICB0aGVtZXMgPSB0aGVtZXMsDQogICAgcmVsZWFzZV9kYXRlID0gcmVsZWFzZV9kYXRlLA0KICAgIHRpdGxlID0gdGl0bGUgLA0KICAgIHJlZnBlciA9IHJlZnBlciwNCiAgICB2YWx1ZSA9IHZhbHVlLA0KICAgIGRhaWx5X3VybCA9IGRhaWx5X3VybCwNCiAgICBkYWlseV90aXRsZSA9IGRhaWx5X3RpdGxlDQogICkNCg0KaW5kaWNlcy5kZiA8LSBhc190aWJibGUobCkNCg0KI2FuZCBkaXNwbGF5IGluIGEgdGFibGUgd2l0aCBzZWxlY3RlZCBjb2x1bW5zDQppbmRpY2VzLmRmICU+JQ0KICBzZWxlY3QoZ2VvX2NvZGUsc291cmNlLHRoZW1lcyx0aXRsZSx2YWx1ZSkgJT4lIA0KICBEVDo6ZGF0YXRhYmxlKHdpZHRoPTYwMCwNCiAgICBjbGFzcyA9ICdjb21wYWN0IHN0cmlwZSBob3ZlciByb3ctYm9yZGVyIG9yZGVyLWNvbHVtbicsDQogICAgcm93bmFtZXMgPSBGQUxTRSwNCiAgICBvcHRpb25zID0gbGlzdCgNCiAgICAgIHBhZ2luZyA9IFRSVUUsDQogICAgICBzZWFyY2hpbmcgPSBUUlVFLA0KICAgICAgaW5mbyA9IEZBTFNFDQogICAgKQ0KICApDQpgYGANCg0KDQoNCg0KYGBge3J9DQppbmRfbGlzdFtbMV1dDQpgYGANCg0KDQpgYGB7cn0NCmhlYWQoaW5kaWNlcy5kZikNCmBgYA0KDQpZb3UgY2FuIHNlYXJjaCBmb3IgYW4gaXRlbSBvZiBpbnRlcmVzdCBlLmcgdHJ5IOKAnFBvdGF0b+KAnSBhbmQgeW91IGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBvbmUgZW50cnkgd2hpY2ggYXBwZWFycyB0byBzaG93IDM0NCw4ODQgYWNyZXMgb2YgUG90YXRvZXMgd2VyZSBwbGFudGVkIGluIENhbmFkYSB0aGlzIHllYXIsIG1vcmUgdGhhbiBlbm91Z2ggdG8gY292ZXIgUGhvZW5peOKAmXMgY2l0eSBsaW1pdHMNCg0KTG9va2luZyBiYWNrIGF0IHRoZSBsaXN0dmlld2VyIHdlIGNhbiBzZWUgdGhhdCB0d28gb2YgdGhlIHRhYmxlIGNvbHVtbnMgZ2VvX2NvZGUgYW5kIHRoZW1lcyBhcHBlYXIgdG8gaGF2ZSBlcXVpdmFsZW50IHJhdyBkYXRhLiBMZXTigJlzIHRhYnVsaXplIHRoZW0gYXMgd2VsbC4gSXTigJlzIGVhc2llciB0aGUgc2Vjb25kIHRpbWUgdGhyb3VnaC4gRm9yIGFueSBGcmFuY29waGlsZXMsIGp1c3Qgc3dhcCBpbiB0aGUgRnJlbmNoIGFsdGVybmF0aXZlDQoNCg0KDQoNCg0KDQpgYGB7cn0NCmdlb19saXN0IDwtIHBhcnNlZCRyZXN1bHRzJGdlbw0KDQpnZW9fY29kZSA8LSBtYXBfY2hyKGdlb19saXN0LCAiZ2VvX2NvZGUiKQ0KZ2VvX25hbWUgPC0gbWFwX2NocihnZW9fbGlzdCwgYygyLDEpKQ0KDQoNCmwgPC0NCiAgbGlzdCgNCiAgICBnZW9fY29kZT1nZW9fY29kZSwNCiAgICBnZW9fbmFtZT1nZW9fbmFtZQ0KICAgICkNCg0KZ2VvLmRmIDwtIGFzX3RpYmJsZShsKQ0KDQpnZW8uZGYgJT4lDQogIERUOjpkYXRhdGFibGUoDQogICAgY2xhc3MgPSAnY29tcGFjdCBzdHJpcGUgaG92ZXIgcm93LWJvcmRlciBvcmRlci1jb2x1bW4nLA0KICAgIHJvd25hbWVzID0gRkFMU0UsDQogICAgb3B0aW9ucyA9IGxpc3QoDQogICAgICBwYWdpbmcgPSBUUlVFLA0KICAgICAgc2VhcmNoaW5nID0gVFJVRSwNCiAgICAgIGluZm8gPSBGQUxTRQ0KICAgICkNCiAgKQ0KYGBgDQoNCg0KYGBge3J9DQojIyBzaW1pbGFyIGZvciB0aGVtZXMgLSBwcm9iYWJseSBhIG1hcF9kZiBhbHRlcm5hdGl2ZQ0KDQp0aGVtZV9saXN0IDwtIHBhcnNlZCRyZXN1bHRzJHRoZW1lc19lbg0KDQoNCg0KdGhlbWVfY29kZSA8LSBtYXBfY2hyKHRoZW1lX2xpc3QsIDEpDQp0aGVtZV9uYW1lIDwtIG1hcF9jaHIodGhlbWVfbGlzdCwgMikNCg0KDQpsIDwtDQogIGxpc3QoDQogICB0aGVtZV9jb2RlPXRoZW1lX2NvZGUsDQogICAgdGhlbWVfbmFtZT10aGVtZV9uYW1lDQogICAgKQ0KDQp0aGVtZS5kZiA8LSBhc190aWJibGUobCkNCg0KdGhlbWUuZGYgJT4lDQogIERUOjpkYXRhdGFibGUoDQogICAgY2xhc3MgPSAnY29tcGFjdCBzdHJpcGUgaG92ZXIgcm93LWJvcmRlciBvcmRlci1jb2x1bW4nLA0KICAgIHJvd25hbWVzID0gRkFMU0UsDQogICAgb3B0aW9ucyA9IGxpc3QoDQogICAgICBwYWdpbmcgPSBUUlVFLA0KICAgICAgc2VhcmNoaW5nID0gVFJVRSwNCiAgICAgIGluZm8gPSBGQUxTRQ0KICAgICkNCiAgKQ0KYGBgDQoNCndlIGNhbiBub3cgbGluayB0aGUgZ2VvIGRhdGEuZnJhbWVzIHRvIG1ha2UgdGhlIHRhYmUgbW9yZSBtZWFuaW5nZnVsDQoNCg0KYGBge3J9DQppbmRpY2VzLmRmICU+JSANCiAgbGVmdF9qb2luKGdlby5kZikgJT4lIA0KICBzZWxlY3QodGl0bGUscmVmcGVyLGdlb19uYW1lLHRoZW1lcyx2YWx1ZSxzb3VyY2UpJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgRFQ6OmRhdGF0YWJsZShjbGFzcz0nY29tcGFjdCBzdHJpcGUgaG92ZXIgcm93LWJvcmRlciBvcmRlci1jb2x1bW4nLHJvd25hbWVzPUZBTFNFLG9wdGlvbnM9IGxpc3QocGFnaW5nID0gVFJVRSwgc2VhcmNoaW5nID0gVFJVRSxpbmZvPUZBTFNFKSkNCmBgYA0KDQpUeXBlIGluIOKAmG9uZS1wZXJzb27igJkgYW5kIHlvdSB3aWxsIHNlZSB0aGF0IHRoZSBQcm9wb3J0aW9uIG9mIG9uZS1wZXJzb24gaG91c2Vob2xkcyByYW5nZXMgYnkgcHJvdmluY2UgZnJvbSAxOC45JSBpbiBOdW5hdnV0IHRvIDMzLjMlIGluIFF1ZWJlYw0KDQpOb3cgbGV0cyBzZWFyY2ggZm9yIGluZGljYXRvcnMgcmVsYXRpbmcgdG8gdGhlIHRoZW1lIG9mIOKAmEFncmljdWx0dXJl4oCZIHdoaWNoIGhhcyBhIGNvZGUgb2YgOTIwLiBFbnRlciA5MjAgaW4gdGhlIHNlYXJjaCBhbmQgeW91IGNhbiBmaW5kIG91dCBpZiB2ZWdldGFibGVzIGFyZSB3b3J0aCBtb3JlIHRvIHRoZSBDYW5hZGlhbiBlY29ub215IHRoYW4gZnJ1aXRzDQoNCk5vdGUgdGhlIGZpbmFsIGNvbHVtbiwgc291cmNlLiBJZiB5b3UgZW50ZXIgdGhlIGZydWl0cyB2YWx1ZSDigJgxMDAwOeKAmSBpbnRvIHRoZSBDQU5TSU0gc2VhcmNoIGZpZWxkIHlvdSB3aWxsIGdldCBmb3J3YXJkZWQgdG8gYSB0YWJsZSBmcm9tIHdoaWNoIHRoZSB1bmRlcmx5aW5nIGluZGljYXRvciBoYXMgYmVlbiBleHRyYWN0ZWQNCg0KDQpOQiBUaGlzIHNlYXJjaC9icm93c2UgcGFnZShodHRwOi8vd3d3NS5zdGF0Y2FuLmdjLmNhL2NhbnNpbS9hMDE/bGFuZz1lbmcpIGFsc28gaGFzIGxpbmtzIHRvIGFsbCB0YWJsZXMsIG5vdCBqdXN0IHRob3NlIGZvciB3aGljaCB0aGVyZSBhcmUgaW5kaWNhdG9ycy4gVGhlc2Ugd291bGQgbmVlZCB0byBiZSBzY3JhcGVkLCBjdXJyZW50bHkNCg0KDQpUaGlzIGlzIGEgc3Vic2V0IG9mIGEgZGF0YSB0YWJsZSB3aXRoIHByb3ZpbmNpYWwgYnJlYWtkb3ducyBvdmVyIGEgZ3JlYXRlciB0aW1lIHBlcmlvZA0KDQpUaGUgQ0FOU0lNIHByb2Nlc3MgaXMgdGhhdCB5b3UgbWFuaXB1bGF0ZSBvbi1saW5lIHRoZSBkYXRhIHlvdSB3YW50IGFuZCB0aGVuIHlvdSBjYW4gZG93bmxvYWQgYSBjc3YuIFNvLCBpZiwgZm9yIGV4YW1wbGUsIGFsbCB5b3Ugd2VyZSBpbnRlcmVzdGVkIGluIHdhcyB0b25uYWdlIG9mIHBlYXJzIGZyb20gUEVJIGZvciB0aGUgeWVhcnMgMjAwMS0yMDA2IChhbnN3ZXIgbm90IG11Y2gpIHRoaXMgbWlnaHQgYmUgdGhlIGJlc3Qgd2F5IHRvIHByb2NlZWQNCg0KSG93ZXZlciwgb2Z0ZW4gaXQgaXMgYmV0dGVyIGp1c3QgdG8gZG93bmxvYWQgYWxsIHBvdGVudGlhbGx5LXJlbGV2YW50IGRhdGEgYW5kIHRoZW4gZG8gc29tZSBleHBsb3JhdG9yeSBhbmFseXNlcyB3aXRoaW4gUi4gVGhpcyBpcyBmZWFzaWJsZSBidXQgc3RpbGwgbmVlZHMgYSBmZXcgY2xpY2tzIGFuZCBtb3ZpbmcgdGhlIGRvd25sb2FkZWQgZmlsZSB0byB0aGUgYXBwcm9wcmlhdGUgZm9sZGVyIGFuZCB0aGVuIGltcG9ydGluZyBpdCBpbnRvIFIuIEkga25vdywgSSB3YW50IHRoZSBlYXN5IGxpZmUNCg0KRW50ZXIgQ0FOU0lNMlIgYW4gUiBwYWNrYWdlIHdoaWNoIOKAmERpcmVjdGx5IEV4dHJhY3RzIENvbXBsZXRlIENBTlNJTSBEYXRhIFRhYmxlc+KAmS4gVGhpcyB3YXMgZGV2ZWxvcGVkIGEgY291cGxlIG9mIHllYXJzIGFnbyBieSBNYXJjbyBMdWdvIHdoZW4gaGUgd2FzIGF0IHRoZSBVbml2ZXJzaXR5IG9mIE1vbnRyZWFsIGFuZCBoYXMgYSBjb3VwbGUgb2YgZnVuY3Rpb25zIC0gb25lIG9mIHdoaWNoLCBnZXRDQU5TSU0oKSwgZXh0cmFjdHMgYSBjb21wbGV0ZSBDQU5TSU0gKFN0YXRpc3RpY3MgQ2FuYWRhKSBkYXRhIHRhYmxlIGFuZCBjb252ZXJ0cyBpdCBpbnRvIGEgcmVhZGlseSB1c2FibGUgcGFuZWwgKHdpZGUpIGZvcm1hdC4NCg0KSSBkaWQgbm90IGZpbmQgdGhpcyBhIHBhcnRpY3VsYWx5IHVzZWZ1bCBlbmQtcHJvZHVjdCAodHJ5IHRoZSB2aWduZXR0ZSBmb3IgYW4gZXhhbXBsZSkgYnV0IHRoZSBiZWhpbmQtdGhlLXNjZW5lcyBjb2RlIHdhcyB2YWx1YWJsZS4gSXQgd2FzIFBUIChQcmUtVGlkeXZlcnNlKSBidXQgd29ya3MganVzdCBmaW5lLiBGb3Igc29tZSByZWFzb24gdGhlIGZydWl0IHRhYmxlIGNvZGUgZGlkIG5vdCB3b3JrIHNvIEkgc3Vic3RpdHV0ZWQg4oCmLg0KDQoNCiMjIyBGdW5kaW5nIG9mIFJlc2VhcmNoIGFuZCBkZXZlbG9wbWVudCBleHBlbmRpdHVyZXMgaW4gdGhlIGhpZ2hlciBlZHVjYXRpb24gc2VjdG9yDQoNCg0KDQoNCg0KDQoNCiMjIyBDQU5TSU0yUg0KDQoNCmh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9DQU5TSU0yUi9pbmRleC5odG1sDQoNCg0KDQpodHRwczovL3d3dzE1MC5zdGF0Y2FuLmdjLmNhL24xL3RibC9jc3YvMjcxMDAwMjUtZW5nLnppcA0KDQpodHRwOi8vd3d3MjAuc3RhdGNhbi5nYy5jYS90YWJsZXMtdGFibGVhdXgvY2Fuc2ltL2Nzdi8NCg0KDQpgYGB7cn0NCmxpYnJhcnkoSG1pc2MpDQpsaWJyYXJ5KHV0aWxzKQ0KDQoNCiMjIyMjIyMjIyMjIyMNCmNyZWF0ZVN0YXRDYW5WYXJpYWJsZXMgPC0gZnVuY3Rpb24oZGYpew0KICBWZWN0b3JQb3NpdGlvbiA8LSBtYXRjaCgiVmVjdG9yIixuYW1lcyhkZikpDQoNCiAgI09ubHkgY3JlYXRlIG5ldyB2YXJpYWJsZSBpZiB0aGVyZSBpcyBtb3JlIHRoYW4gb25lIGNvbHVtbiBmcm9tIFN0YXRDYW4NCiAgaWYoVmVjdG9yUG9zaXRpb24gPiA0KSBkZiRTdGF0Q2FuVmFyaWFibGUgPC0gYXBwbHkoZGZbLGMoMzooVmVjdG9yUG9zaXRpb24tMSkpXSwgMSwgZnVuY3Rpb24oeCkgcGFzdGUoeCwgY29sbGFwc2UgPSAiOyAiKSkNCiAgZWxzZSBkZiRTdGF0Q2FuVmFyaWFibGUgPC0gZGZbLDNdDQoNCiAgcmV0dXJuKGRmKQ0KfQ0KDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMNCmRvd25sb2FkQ0FOU0lNIDwtIGZ1bmN0aW9uKGNhbnNpbVRhYmxlTnVtYmVyLCByYXcgPSBGQUxTRSwgbGFuZyA9ICJlbmciKXsNCiAgIyB2YWxpZGF0aW9uIG9mIHRoZSBsYW5nIHBhcmFtZXRlcg0KICAjIHRoYW5rcyB0byBQcm9mZXNzb3IgSmVhbi1IZXJtYW4gR3VheSAoVW5pdmVyc2l0w6kgZGUgU2hlcmJyb29rZSkgZm9yIHN1Z2dlc3RpbmcgdGhlIGluY2x1c2lvbiBvZiBGcmVuY2ggZGF0YSBsYWJlbHMNCiAgc2VwYXJhdG9yID0gJywnDQogIGlmKGxhbmcgPT0gImVuZyIpIGxhbmcgPSAiLWVuZyINCiAgZWxzZSBpZihsYW5nID09ICJmcmEiIHx8IGxhbmcgPT0gImZyIil7DQogICAgbGFuZyA9ICItZnJhIg0KICAgIHNlcGFyYXRvciA9ICc7Jw0KICB9IA0KICBlbHNlIHsNCiAgICBwcmludCgiT25seSBFbmdsaXNoIChlbmcpIGFuZCBGcmVuY2ggKGZyYSkgYXJlIGFjY2VwdGVkIHZhbHVlcyBmb3IgbGFuZy4gRGVmYXVsdGluZyB0byBFbmdsaXNoLiIpDQogICAgbGFuZyA9ICItZW5nIg0KICB9DQogIA0KICB0ZW1wIDwtIHRlbXBmaWxlKCkgIyBjcmVhdGUgYSB0ZW1wb3JhcnkgZmlsZSB0byBzdG9yZSB0aGUgZG93bmxvYWRlZCBkYXRhDQogICMgY3JlYXRlIHRoZSB1cmwgdG8gZG93bmxvYWQgdGhlIENBTlNJTSBkYXRhIGFjY29yZGluZyB0byB0aGUgdXNlcidzIG5lZWRzDQogIHVybCA8LSAiaHR0cHM6Ly93d3cxNTAuc3RhdGNhbi5nYy5jYS9uMS9lbi90YmwvY3N2LyINCiAgY2Fuc2ltVGFibGVOdW1iZXIgPC0gZ3N1YignLScsICcnLCBjYW5zaW1UYWJsZU51bWJlcikNCiAgY2Fuc2ltVGFibGVOdW1iZXJTdHJpbmcgPC0gc3ByaW50ZigiJTA4ZCIsIGFzLm51bWVyaWMoY2Fuc2ltVGFibGVOdW1iZXIpKSAjUHV0IHRoZSBjb3JyZWN0IGFtb3VudCBvZiBsZWFkaW5nIHplcm9lczsgcGFzdGUwIHVzZXMgYXMuY2hhcmFjdGVyIHdoaWNoIHRydW5jYXRlcyBsZWFkaW5nIHplcm9lcyBmcm9tIGludGVnZXJzIChzcGVjaWFsIHRoYW5rcyB0byBTb2hlaWwgc29oZWlsIE1haG1vb2R6YWRlaCBmb3IgcmVwb3J0aW5nIHRoZSBidWcpDQogIGZpbGVuYW1lIDwtIHBhc3RlMChjYW5zaW1UYWJsZU51bWJlclN0cmluZywgbGFuZykNCiAgY3N2X2ZpbGVuYW1lIDwtIHBhc3RlMChjYW5zaW1UYWJsZU51bWJlclN0cmluZywgIi5jc3YiKQ0KICB1cmwgPC0gcGFzdGUwKHVybCwgZmlsZW5hbWUsICIuemlwIikNCiAgDQogIHRyeUNhdGNoKA0KICAgIHsNCiAgICBkb3dubG9hZCh1cmwsIHRlbXAsIHF1aWV0ID0gVFJVRSwgbW9kZSA9ICJ3YiIpICMgZnJvbSB0aGUgZG93bmxvYWRlciBwYWNrYWdlLCBlYXNpbHkgaGFuZGxlcyBjcm9zcy1wbGFmb3JtIGh0dHBzIHJlcXVlc3RzLCB3cmFwcGVyIGZvciBkb3dubG9hZC5maWxlDQogICAgfSwNCiAgICBlcnJvcj1mdW5jdGlvbihlcnIpeyByZXR1cm4oLTEpIH0sDQogICAgd2FybmluZz1mdW5jdGlvbih3YXJuKXsgcmV0dXJuKC0xKSB9DQogICAgKQ0KICB0ZW1wX2ZpbGVzaXplIDwtIGZpbGUuaW5mbyh0ZW1wKSRzaXplDQogIA0KICBpZihpcy5uYSh0ZW1wX2ZpbGVzaXplKSB8fCB0ZW1wX2ZpbGVzaXplID09IDApIHJldHVybihOQSkgIyBmaWxlIGlzIG5vbi1leGlzdGVudCwgZXhpdCBwcmVtYXR1cmVseQ0KICANCiAgZGF0YSA8LSByZWFkLmNzdih1bnoodGVtcCwgY3N2X2ZpbGVuYW1lKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFLCBzZXAgPSBzZXBhcmF0b3IsIGVuY29kaW5nID0gIlVURi04IikNCiAgdW5saW5rKHRlbXApDQogIGlmKHJhdyA9PSBUUlVFKSByZXR1cm4oZGF0YSkgI2lmIHJhdyBlcXVhbHMgVFJVRSwgdGhlbiB0aGUgcmF3IGRvd25sb2FkIGlzIHJldHVybmVkOyBmdW5jdGlvbmFsaXR5IHN1Z2dlc3RlZCBieSBTb2hlaWwgTWFobW9vZHphZGVoDQoNCiAgbmFtZXMoZGF0YSkgPC0gaWNvbnYobmFtZXMoZGF0YSksIHRvPSdBU0NJSS8vVFJBTlNMSVQnKSAjIHJlbW92ZSBhY2NlbnRzIGZyb20gdmFyaWFibGUgbmFtZXMNCiAgDQogIGRhdGEkREdVSUQgPC0gTlVMTA0KICBkYXRhJElERU5USUZJQ0FURVVSLkQuVU5JVEUuREUuTUVTVVJFIDwtIE5VTEwNCiAgZGF0YSRVT01fSUQgPC0gTlVMTA0KICBkYXRhJFNDQUxBUl9GQUNUT1IgPC0gTlVMTA0KICBkYXRhJEZBQ1RFVVIuU0NBTEFJUkUgPC0gTlVMTA0KICBkYXRhJFNDQUxBUl9JRCA8LSBOVUxMDQogIGRhdGEkSURFTlRJRklDQVRFVVIuU0NBTEFJUkUgPC0gTlVMTA0KDQogIGRhdGEgPC0gY3JlYXRlU3RhdENhblZhcmlhYmxlcyhkYXRhKQ0KICANCiAgZGF0YSRWRUNUT1IgPC0gTlVMTA0KICBkYXRhJFZFQ1RFVVIgPC0gTlVMTA0KICBkYXRhJENPT1JESU5BVEVTIDwtIE5VTEwNCiAgZGF0YSRDT09SRE9ORUVTIDwtIE5VTEwNCg0KICBpZihsYW5nID09ICctZnJhJykgc3VwcHJlc3NXYXJuaW5ncyhkYXRhJFZBTEVVUiA8LSBhcy5udW1lcmljKGRhdGEkVkFMRVVSKSkNCiAgZWxzZSAgICAgICAgICAgICAgIHN1cHByZXNzV2FybmluZ3MoZGF0YSRWQUxVRSA8LSBhcy5udW1lcmljKGRhdGEkVkFMVUUpKQ0KDQogIHJldHVybihkYXRhKQ0KfQ0KDQpgYGANCg0KDQoNCg0KYGBge3J9DQoNCmRmX3JhdyA8LSBkb3dubG9hZENBTlNJTSgwMDAxMDAwOSkNCiMgZGZfcmF3IDwtIGRvd25sb2FkQ0FOU0lNKDM1ODAxNjIpDQoNCg0KZGZfcmF3ICU+JQ0KICAgIERUOjpkYXRhdGFibGUoY2xhc3M9J2NvbXBhY3Qgc3RyaXBlIGhvdmVyIHJvdy1ib3JkZXIgb3JkZXItY29sdW1uJyxyb3duYW1lcz1GQUxTRSxvcHRpb25zPSBsaXN0KHBhZ2luZyA9IFRSVUUsIHNlYXJjaGluZyA9IFRSVUUsaW5mbz1GQUxTRSkpDQpgYGANCg0KDQoNCmBgYHtyfQ0KaGVhZChkZl9yYXcpDQpgYGANCg0KDQoNCg0KDQo=